﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace TaDMS.Models
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected DbContext Model;
        protected DbSet<T> DbSet;

        public string ErrorMessage { get; set; }

        public Repository(DbContext model)
        {
            this.Model = model;
            this.DbSet = Model.Set<T>();
        }

        public void Dispose()
        {
            if (Model != null)
            {
                Model.Dispose();
            }
        }

        public virtual IEnumerable<T> GetData(out int? total, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, bool>>> multiFilter = null, string includeProperties = "", bool isPaging = false, int start = 0, int limit = 50)
        {
            try
            {
                IQueryable<T> query = DbSet;

                if (filter != null)
                {
                    query = query.Where(filter);
                }

                query = includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));


                if (multiFilter != null && multiFilter.Any())
                {
                    query = multiFilter.Aggregate(query, (current, expression) => current.Where(expression));
                }

                if (orderBy != null)
                {
                    query = orderBy(query);
                }

                if (isPaging)
                {
                    total = query.Count();
                    //result = query.ToList();

                    return query.Skip(start).Take(limit);
                }
                else
                {
                    total = null;

                    return query;
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                total = null;

                return null;
            }
        }

        public virtual bool? Contains(Expression<Func<T, bool>> predicate)
        {
            try
            {
                return DbSet.Count(predicate: predicate) > 0;
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual T Find(Expression<Func<T, bool>> predicate)
        {
            try
            {
                return DbSet.FirstOrDefault(predicate: predicate);
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual T Create(T t)
        {
            try
            {
                if (t != null)
                {
                    var newEntity = DbSet.Add(t);

                    int row = Model.SaveChanges();

                    return row > 0 ? newEntity : null;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public int? Create(List<T> t)
        {
            try
            {
                if (t != null)
                {
                    foreach (var en in t)
                    {
                        DbSet.Add(en);
                    }

                    int row = Model.SaveChanges();

                    return row;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual int? Delete(T t)
        {
            try
            {
                if (t != null)
                {
                    DbSet.Attach(t);

                    DbSet.Remove(t);

                    var row = Model.SaveChanges();

                    return row == 0 ? (int?)null : row;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.InnerException.InnerException.Message;

                return null;
            }
        }

        public virtual int? Delete(Expression<Func<T, bool>> predicate)
        {
            try
            {
                var entityToRemove = DbSet.FirstOrDefault(predicate);

                return this.Delete(entityToRemove);
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual int? Update(T t)
        {
            try
            {
                if (t != null)
                {
                    DbSet.Attach(t);

                    Model.Entry(t).State = EntityState.Modified;

                    var row = Model.SaveChanges();

                    return row == 0 ? (int?)null : row;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public int? Update(T t, string fields)
        {
            try
            {
                if (t != null)
                {
                    DbSet.Attach(t);

                    foreach (var field in fields.Split(new []{"$"}, StringSplitOptions.RemoveEmptyEntries))
                    {
                        Model.Entry(t).Property(field).IsModified = true;
                    }

                    var row = Model.SaveChanges();

                    return row == 0 ? (int?)null : row;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }
    }

#region UnitTest
    public class RepositoryUT<T> : IRepository<T> where T : class
    {
        internal List<T> DataTest;
        internal IQueryable<T> DbSet;

        public string ErrorMessage { get; set; }

        public RepositoryUT(IEnumerable<T> dataTest)
        {
            var enumerable = dataTest as IList<T> ?? dataTest.ToList();
            this.DataTest = enumerable.ToList();
            this.DbSet = enumerable.AsQueryable();
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public IEnumerable<T> GetData(out int? total, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, Expression<Func<T, bool>> filter = null, string includeProperties = "",
                                   bool isPaging = false, int start = 0, int limit = 50)
        {
            try
            {
                IQueryable<T> query = DbSet;
                IEnumerable<T> result = null;

                if (filter != null)
                {
                    query = query.Where(filter);
                }

                query = includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));

                result = orderBy != null ? orderBy(query).ToList() : query.ToList();

                if (isPaging)
                {
                    total = query.Count();

                    return result.Skip(start).Take(limit);
                }
                else
                {
                    total = null;

                    return result;
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                total = null;

                return null;
            }
        }

        public IEnumerable<T> GetData(out int? total, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, bool>>> multiFilter = null, string includeProperties = "", bool isPaging = false, int start = 0, int limit = 50)
        {
            throw new NotImplementedException();
        }

        public virtual bool? Contains(Expression<Func<T, bool>> predicate)
        {
            try
            {
                return DbSet.Count(predicate: predicate) > 0;
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual T Find(Expression<Func<T, bool>> predicate)
        {
            try
            {
                return DbSet.FirstOrDefault(predicate: predicate);
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual T Create(T t)
        {
            try
            {
                if (t != null)
                {
                    var newEntity = t;
                    var oldCount = DataTest.Count;
                    DataTest.Add(t);

                    return DataTest.Count - oldCount == 1 ? newEntity : null;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public int? Create(List<T> t)
        {
            throw new NotImplementedException();
        }

        public virtual int? Delete(T t)
        {
            try
            {
                if (t != null)
                {
                    return DataTest.Remove(t) ? 1 : (int?) null;
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual int? Delete(Expression<Func<T, bool>> predicate)
        {
            try
            {
                var entityToRemove = DbSet.FirstOrDefault(predicate);

                return this.Delete(entityToRemove);
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public virtual int? Update(T t)
        {
            try
            {
                if (t != null)
                {
                    int oldCount = DataTest.Count;
                    if (this.Delete(t) == 1 && !String.IsNullOrEmpty(this.ErrorMessage))
                    {
                        this.Create(t);

                        return DataTest.Count == oldCount ? 1 : (int?) null;
                    }
                    else
                    {
                        throw new Exception(message: "Update False: " + this.ErrorMessage);
                    }
                }
                else
                {
                    throw new Exception(message: "Entity is null");
                }
            }
            catch (Exception exception)
            {
                this.ErrorMessage = exception.Message;

                return null;
            }
        }

        public int? Update(T t, string fields)
        {
            throw new NotImplementedException();
        }
    }
#endregion
}